package com.seed.uaa.web;

import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import com.mybatisflex.core.query.QueryChain;
import com.mybatisflex.core.query.QueryCondition;
import com.seed.core.enumeration.AccountType;
import com.seed.core.exception.LoginException;
import com.seed.core.exception.UserAuthenticateException;
import com.seed.core.exception.UserDisabledException;
import com.seed.core.pojo.*;
import com.seed.uaa.tenant.web.Tenant;
import com.seed.uaa.tenant.web.TenantService;
import com.seed.uaa.util.AncestorUtil;
import com.seed.uaa.util.EncryptUtil;
import com.seed.uaa.util.LoginCheckUtil;
import com.seed.uaa.util.StpKit;
import com.seed.uaa.web.organization.Organization;
import com.seed.uaa.web.organization.OrganizationService;
import com.seed.uaa.web.user.User;
import com.seed.uaa.web.user.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

import static com.seed.uaa.tenant.web.table.TenantTableDef.TENANT;
import static com.seed.uaa.web.organization.table.OrganizationTableDef.ORGANIZATION;
import static com.seed.uaa.web.user.table.UserTableDef.USER;

@Service
public class UaaService {
    private final Logger log = LoggerFactory.getLogger(UaaService.class);
    private final UaaProperties config;
    private final UserService userService;
    private final OrganizationService organizationService;
    private TenantService tenantService;

    public UaaService(UaaProperties config,
                      UserService userService,
                      OrganizationService organizationService,
                      @Autowired(required = false) TenantService tenantService
    ) {
        this.config = config;
        this.userService = userService;
        this.organizationService = organizationService;
        this.tenantService = tenantService;
    }

    public void login(LoginDTO dto) {
        String username = dto.getUsername();
        String password = dto.getPassword();

        Account account = new Account(username);
        AccountType type = account.getType();

        boolean isTenant = switch (type) {
            case tenantUsername, tenantUserCode, tenantUserUsername -> true;
            default -> false;
        };
        if(isTenant && tenantService == null) throw new UserAuthenticateException("用户名或密码错误");

//        switch (account.getType()) {
//            case admin -> adminLogin(password);
//            case userCode -> userLoginByCode(account, password);
//            case userUsername -> userLoginByUsername(account, password);
//            case tenantUsername -> tenantLoginByUsername(account, password);
//            case tenantUserCode -> tenantUserLoginByCode(account, password);
//            case tenantUserUsername -> tenantUserLoginByUsername(account, password);
//        }

        String device = dto.getDevice();
        Boolean remember = dto.getRemember();
        Long timeout = dto.getTimeout();
        log.info("请求数据：账号[{}] 设备[{}] 记住我[{}] 超时时间[{}]", username, device, remember, timeout);
        String loginId = account.getLoginId();
        StpKit.login(loginId);
    }

    public Subject switchTo(String loginId) {
        log.info("切换到【{}】", loginId);
        Subject ret = getSubject(loginId);
        StpKit.getSession().set(Const.SWITCH_TO, ret);

        return ret;
    }

    public void endSwitch() {
        StpKit.getSession().delete(Const.SWITCH_TO);
    }

    public Subject profile() {
//        Subject ret = new Subject();
//        String loginId = (String) StpKit.getLoginId();
//        System.out.println("loginId=====" + loginId);
//        Account account = new Account(loginId);
//        String value = account.getValue();
//        switch (account.getType()) {
//            case admin -> ret.setName(AccountType.admin.getLabel());
//            case userCode -> {
//                Organization organization = QueryChain.of(Organization.class)
//                        .where(ORGANIZATION.CODE.eq(value))
//                        .one();
//                User user = QueryChain.of(User.class)
//                        .where(USER.ID.eq(organization.getManagerId()))
//                        .one();
//                ret.setName(organization.getName());
//                ret.setAvatar(user.getAvatar());
//            }
//            case userUsername -> {
//                User user = QueryChain.of(User.class)
//                        .where(USER.USERNAME.eq(value))
//                        .one();
//                ret.setName(user.getName());
//                ret.setAvatar(user.getAvatar());
//            }
//        }
//
//        return ret;
        return null;
    }

    public List<Node<Object>> switchable() {
        Subject subject = getSubject();
        if(subject == null) throw new RuntimeException("请先登录");

        Account account = subject.getAccount();
        return switch (account.getType()) {
            case admin -> adminSwitchable();
            case userCode -> userCodeSwitchable(account);
            case userUsername -> userUsernameSwitchable(account);
            case tenantUsername -> tenantUsernameSwitchable();
            case tenantUserCode -> tenantUserCodeSwitchable(account);
            case tenantUserUsername -> tenantUserUsernameSwitchable(account);
        };
    }

    private List<Node<Object>> adminSwitchable() {
        List<Organization> organizations = QueryChain.of(Organization.class).list();
        String sep = String.valueOf(Const.CODE_SEPARATOR);

        List<Node<Object>> nodes = new ArrayList<>();
        List<Node<Object>> roots = new ArrayList<>();
        for(Organization organization: organizations) {
            String code = organization.getCode();
            Node<Object> node = new Node<>();
            node.setValue(String.format("%s:", code));
            node.setLabel(organization.getName());
            node.setData(organization);
            if(code.contains(sep)) {
                nodes.add(node);
            } else {
                roots.add(node);
            }
        }

        List<User> users = QueryChain.of(User.class).list();
        for(User user: users) {
            String username = user.getUsername();
            if(StrUtil.isBlank(username)) continue;

            Node<Object> node = new Node<>();
            node.setValue(username);
            node.setLabel(user.getName());
            node.setData(user);
            if(StrUtil.isBlank(user.getOrganizationId())) {
                roots.add(node);
            } else {
                nodes.add(node);
            }
        }

        for(Node<Object> root: roots) makeTree(root, nodes);

        return roots;
    }

    private List<Node<Object>> userCodeSwitchable(Account account) {
        String value = account.getValue();
        String regex = "\\" + Const.CODE_SEPARATOR;
        int parentLen = value.split(regex).length;
        Organization current = QueryChain.of(Organization.class)
                .where(ORGANIZATION.CODE.eq(value))
                .one();
        if(current == null) throw new RuntimeException("当前组织不存在");

        List<Organization> organizations = QueryChain.of(Organization.class)
                .where(ORGANIZATION.CODE.likeLeft(value + Const.CODE_SEPARATOR))
                .list();

        List<Node<Object>> nodes = new ArrayList<>();
        List<Node<Object>> roots = new ArrayList<>();
        for(Organization organization: organizations) {
            String code = organization.getCode();
            Node<Object> node = new Node<>();
            node.setValue(String.format("%s:", code));
            node.setLabel(organization.getName());
            node.setData(organization);

            int len = code.split(regex).length;
            boolean isChild = code.startsWith(value + Const.CODE_SEPARATOR) && len == parentLen + 1;
            if(isChild) {
                roots.add(node);
            } else {
                nodes.add(node);
            }
        }

        Object[] organizationIds = organizations.stream().map(Organization::getId).toArray();
        if(organizationIds.length > 0) {
            List<User> users = QueryChain.of(User.class)
                    .where(USER.ORGANIZATION_ID.in(organizationIds))
                    .list();
            for(User user: users) {
                String username = user.getUsername();
                if(StrUtil.isBlank(username)) continue;

                Node<Object> node = new Node<>();
                node.setValue(username);
                node.setLabel(user.getName());
                node.setData(user);
                if(current.getId().equals(user.getOrganizationId())) {
                    roots.add(node);
                } else {
                    nodes.add(node);
                }
            }
        }

        for(Node<Object> root: roots) makeTree(root, nodes);

        return roots;
    }

    private List<Node<Object>> userUsernameSwitchable(Account account) {
        String value = account.getValue();
        User current = QueryChain.of(User.class).where(USER.USERNAME.eq(value)).one();
        if(current == null) throw new RuntimeException("当前用户不存在");

        List<Organization> positions = QueryChain.of(Organization.class)
                .where(ORGANIZATION.MANAGER_ID.eq(current.getId()))
                .list();
        if(positions.isEmpty()) return Collections.emptyList();

        List<Organization> rootPositions = filterDescendant(positions);
        List<Node<Object>> nodes = new ArrayList<>();
        List<Node<Object>> roots = new ArrayList<>();
        for(Organization organization: rootPositions) {
            String code = organization.getCode();
            Node<Object> node = new Node<>();
            node.setValue(String.format("%s:", code));
            node.setLabel(organization.getName());
            node.setData(organization);

            roots.add(node);
        }

        QueryCondition queryCondition = QueryCondition.createEmpty();
        for(Organization position: rootPositions) {
            String code = position.getCode();
            queryCondition.or(ORGANIZATION.CODE.likeLeft(code + Const.CODE_SEPARATOR));
        }

        List<Organization> organizations = QueryChain.of(Organization.class).where(queryCondition).list();
        for(Organization organization: organizations) {
            String code = organization.getCode();
            Node<Object> node = new Node<>();
            node.setValue(String.format("%s:", code));
            node.setLabel(organization.getName());
            node.setData(organization);

            nodes.add(node);
        }

        Object[] positionIds = rootPositions.stream().map(Organization::getId).toArray();
        Object[] organizationIds = organizations.stream().map(Organization::getId).toArray();
        List<Object> ids = new ArrayList<>(List.of(positionIds));
        ids.addAll(List.of(organizationIds));

        List<User> users = QueryChain.of(User.class).where(USER.ORGANIZATION_ID.in(ids)).list();
        for(User user: users) {
            String username = user.getUsername();
            if(StrUtil.isBlank(username) || user.getId().equals(current.getId())) continue;

            Node<Object> node = new Node<>();
            node.setValue(username);
            node.setLabel(user.getName());
            node.setData(user);
            nodes.add(node);
        }

        for(Node<Object> root: roots) makeTree(root, nodes);

        return roots;
    }

    private List<Organization> filterDescendant(List<Organization> organizations) {
        if(organizations.isEmpty()) return organizations;

        List<String> descendantIds = new ArrayList<>();
        for(Organization organization: organizations) {
            if(isAnyDescendant(organization, organizations)) {
                descendantIds.add(organization.getId());
            }
        }

        return organizations.stream().filter(e -> !descendantIds.contains(e.getId())).toList();
    }

    private boolean isAnyDescendant(Organization organization, List<Organization> organizations) {
        String currentCode = organization.getCode();
        for(Organization o: organizations) {
            String code = o.getCode();
            if(code.equals(currentCode)) continue;

            if(currentCode.startsWith(code + Const.CODE_SEPARATOR)) return true;
        }

        return false;
    }

    private List<Node<Object>> tenantUsernameSwitchable() {
        return adminSwitchable();
    }

    private List<Node<Object>> tenantUserCodeSwitchable(Account account) {
        return userCodeSwitchable(account);
    }

    private List<Node<Object>> tenantUserUsernameSwitchable(Account account) {
        return userUsernameSwitchable(account);
    }

    protected void makeTree(Node<Object> parent, List<Node<Object>> nodes) {
        String regex = "\\" + Const.CODE_SEPARATOR;
        List<Node<Object>> children = new ArrayList<>();
        if(parent.getData() instanceof Organization parentOrganization) {
            String parentId = parentOrganization.getId();
            String parentCode = parentOrganization.getCode();
            int parentLen = parentCode.split(regex).length;
            for(Node<Object> node: nodes) {
                Object data = node.getData();
                if(data instanceof User user) {
                    if(parentId.equals(user.getOrganizationId())) children.add(node);
                } else if(data instanceof Organization organization) {
                    String code = organization.getCode();
                    int len = code.split(regex).length;
                    boolean isChild = code.startsWith(parentCode + Const.CODE_SEPARATOR) && len == parentLen + 1;
                    if(isChild) {
                        makeTree(node, nodes);
                        children.add(node);
                    }
                }
            }
            parent.setChildren(children);
        }
    }

    private void adminLogin(String rawPassword) {
        Admin admin = config.getAdmin();
        if(!admin.getEnabled()) {
            throw new UserDisabledException("用户名或密码错误");
        }

        if(!admin.getPassword().equals(rawPassword)) {
            throw new UserDisabledException("用户名或密码错误");
        }

        Date date = admin.getEnableTime();
        if(date != null && date.after(new Date())) {
            throw new UserDisabledException();
        }

        if(!rawPassword.equals(admin.getPassword())) {
            throw new UserAuthenticateException();
        }
    }

    private void userLoginByCode(Account account, String rawPassword) throws LoginException {
        String code = account.getValue();
        Organization organization = QueryChain.of(Organization.class).where(ORGANIZATION.CODE.eq(code)).one();
        LoginCheckUtil.checkLogin(organization);

        String userId = organization.getManagerId();
        if(StrUtil.isBlank(userId)) throw new UserAuthenticateException("组织负责人为空, 不能登录");

        User user = QueryChain.of(User.class).where(USER.ID.eq(userId)).one();
        LoginCheckUtil.checkLogin(user);

        String encrypt = EncryptUtil.encrypt(rawPassword, user.getSalt());
        if(!encrypt.equals(user.getPassword())) {
            throw new UserAuthenticateException("用户名或密码错误");
        }

        checkAncestorLogin(organization); //检查祖先用户是否满足登录条件

//        List<String> ancestorCodeList = AncestorUtil.getAncestorCodeList(code);
//        if(!ancestorCodeList.isEmpty()) {
//            List<Organization> ancestorList = QueryChain.of(Organization.class)
//                    .where(ORGANIZATION.CODE.in(ancestorCodeList))
//                    .list();
//            LoginCheckUtil.checkAncestorLogin(ancestorList); //检查祖先用户是否满足登录条件
//        }
    }

    private void checkAncestorLogin(Organization organization) {
        String parentId = organization.getParentId();
        while (StrUtil.isNotBlank(parentId)) {
            organization = QueryChain.of(Organization.class)
                    .where(ORGANIZATION.ID.eq(parentId))
                    .one();
            LoginCheckUtil.checkAncestorLogin(organization);
            parentId = organization.getParentId();
        }
    }

    private void userLoginByUsername(Account account, String rawPassword) throws LoginException {
        String username = account.getValue();
        User user = QueryChain.of(User.class).where(USER.USERNAME.eq(username)).one();
        LoginCheckUtil.checkLogin(user);

        String encrypt = EncryptUtil.encrypt(rawPassword, user.getSalt());
        if(!encrypt.equals(user.getPassword())) {
            throw new UserAuthenticateException("用户名或密码错误");
        }

        // 任职的岗位
        List<Organization> organizations = QueryChain.of(Organization.class)
                .where(ORGANIZATION.MANAGER_ID.eq(user.getId()))
                .list();
        for(Organization organization: organizations) {
            LoginCheckUtil.checkLogin(organization); //自已是否具备登录条件
            checkAncestorLogin(organization); //检查祖先用户是否满足登录条件
//            List<String> ancestorCodeList = AncestorUtil.getAncestorCodeList(organization.getCode());
//            if(!ancestorCodeList.isEmpty()) {
//                List<Organization> ancestorList = QueryChain.of(Organization.class)
//                        .where(ORGANIZATION.CODE.in(ancestorCodeList))
//                        .list();
//                LoginCheckUtil.checkAncestorLogin(ancestorList); //检查祖先用户是否满足登录条件
//            }
        }
    }

//    private void tenantLoginByUsername(Account account, String rawPassword) {
//        Tenant tenant = QueryChain.of(Tenant.class).where(TENANT.TENANTNAME.eq(account.getValue())).one();
//        checkTenantLogin(tenant);
//
//        if(!EncryptUtil.encrypt(rawPassword, tenant.getSalt()).equals(tenant.getPassword())) {
//            throw new UserAuthenticateException("用户名或密码错误");
//        }
//    }
//
//    private void tenantUserLoginByCode(Account account, String rawPassword) throws LoginException {
//        String tenantname = account.getTenantname();
//        Tenant tenant = QueryChain.of(Tenant.class).where(TENANT.TENANTNAME.eq(tenantname)).one();
//        checkTenantLogin(tenant);
//
//        A.useTenantname(tenantname, () -> userLoginByCode(account, rawPassword));
//    }
//
//    private void tenantUserLoginByUsername(Account account, String rawPassword) throws LoginException {
//        String tenantname = account.getTenantname();
//        Tenant tenant = QueryChain.of(Tenant.class).where(TENANT.TENANTNAME.eq(tenantname)).one();
//        checkTenantLogin(tenant);
//
//        A.useTenantname(tenantname, () -> userLoginByUsername(account, rawPassword));
//    }

    private void checkTenantLogin(Tenant tenant) {
        LoginCheckUtil.checkLogin(tenant);

        String organizationId = tenant.getOrganizationId();
        if(StrUtil.isNotBlank(organizationId)) {
            Organization organization = QueryChain.of(Organization.class)
                    .where(ORGANIZATION.ID.eq(organizationId))
                    .one();
            LoginCheckUtil.checkLogin(organization);

            List<String> ancestorCodeList = AncestorUtil.getAncestorCodeList(organization.getCode());
            if(!ancestorCodeList.isEmpty()) {
                List<Organization> ancestorList = QueryChain.of(Organization.class)
                        .where(ORGANIZATION.CODE.in(ancestorCodeList))
                        .list();
                LoginCheckUtil.checkAncestorLogin(ancestorList); //检查祖先用户是否满足登录条件
            }
        }
    }

    public Subject getSubject() {
        SaSession session = StpKit.getSession();
        Object obj = session.get(Const.SUBJECT);
        if(obj == null) {
            String loginId = StpKit.getLoginIdAsString();
            Subject newSubject = getSubject(loginId);

            SaTokenInfo info = new SaTokenInfo();
            info.tokenName = StpUtil.getTokenName();
            info.tokenValue = StpUtil.getTokenValue();
            info.isLogin = StpUtil.isLogin();
            info.loginId = loginId;
            info.loginType = StpUtil.getLoginType();
            info.tokenTimeout = StpUtil.getTokenTimeout();
            info.sessionTimeout = StpUtil.getSessionTimeout();
            info.tokenSessionTimeout = StpUtil.getTokenSessionTimeout();
            info.tokenActiveTimeout = StpUtil.getTokenActiveTimeout();
//            info.loginDevice = StpUtil.getLoginDevice();
            newSubject.setTokenInfo(info);
            session.set(Const.SUBJECT, newSubject);

            return newSubject;
        }

        return (Subject) obj;
    }

    private Subject getSubject(String loginId) {
//        Account account = new Account(loginId);
//        AccountType type = account.getType();
//        Subject ret = switch (type) {
//            case admin -> new Subject(account).setName("超级管理员").setPermissions(List.of("*"));
//            case userCode -> userSubjectByCode(account);
//            case userUsername -> userSubjectByUsername(account);
//            case tenantUsername -> tenantSubjectByUsername(account);
//            case tenantUserCode -> tenantUserSubjectByCode(account);
//            case tenantUserUsername -> tenantUserSubjectByUsername(account);
//        };
//
//        return switch (type) {
//            case admin, userCode, userUsername -> ret.setFullPermissions(List.of("*"));
//            case tenantUsername, tenantUserCode, tenantUserUsername -> {
//                App app = SeedFacade.getApp();
//                String dataSourceKey = StrKit.dataSourceKey(app.getOwner(), app.getName(), null, null);
//                List<String> tenantPermissions = DataSourceKey.use(dataSourceKey, () -> {
//                    String tenantname = account.getTenantname();
//                    Tenant tenant = QueryChain.of(Tenant.class).where(TENANT.TENANTNAME.eq(tenantname)).one();
//                    return tenantService.getActions(tenant.getId());
//                });
//                yield ret.setFullPermissions(tenantPermissions);
//            }
//        };
        return null;
    }

    private Subject userSubjectByCode(Account account) {
        String code = account.getValue();
        Organization organization = QueryChain.of(Organization.class).where(ORGANIZATION.CODE.eq(code)).one();

        Subject ret = new Subject(account).setName(organization.getName());
        String managerId = organization.getManagerId();
        if(StrUtil.isNotBlank(managerId)) {
            User user = QueryChain.of(User.class).where(USER.ID.eq(organization.getManagerId())).one();
            ret.setAvatar(user.getAvatar());
        }

        List<String> actions = organizationService.getFinalActions(organization.getId());
        return ret.setPermissions(actions);
    }

    private Subject userSubjectByUsername(Account account) {
        String username = account.getValue();
        User user = QueryChain.of(User.class).where(USER.USERNAME.eq(username)).one();

        // 任职的岗位
        List<Organization> positions = QueryChain.of(Organization.class)
                .where(ORGANIZATION.MANAGER_ID.eq(user.getId()))
                .list();
        Map<String, String> subjectPositions = new HashMap<>();
        List<String> permissions = new ArrayList<>(userService.getActions(user.getId()));
        String tenantname = account.getTenantname();
        for(Organization position: positions) {
            String loginId = (StrUtil.isBlank(tenantname) ? "" : Const.COLON + tenantname + Const.COLON) + position.getCode() + Const.COLON;
            subjectPositions.put(loginId, position.getName());

            List<String> actions = organizationService.getHaveActions(position.getId());
            permissions.addAll(actions);
        }

        return new Subject(account)
                .setName(user.getName())
                .setAvatar(user.getAvatar())
                .setPermissions(permissions.stream().distinct().toList())
                .setPositions(subjectPositions);
    }

    private Subject tenantSubjectByUsername(Account account) {
        String tenantname = account.getValue();
        Tenant tenant = QueryChain.of(Tenant.class).where(TENANT.TENANTNAME.eq(tenantname)).one();

        List<String> actions = tenantService.getActions(tenant.getId());
        return new Subject(account)
                .setName(tenant.getName())
                .setAvatar(tenant.getLogo())
                .setPermissions(actions);
    }

//    private Subject tenantUserSubjectByCode(Account account) {
//        return A.useTenantname(account.getTenantname(), () -> userSubjectByCode(account));
//    }
//
//    private Subject tenantUserSubjectByUsername(Account account) {
//        return A.useTenantname(account.getTenantname(), () -> userSubjectByUsername(account));
//    }
}
